package org.ecbox.sdk;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;

import org.ecbox.sdk.internal.parser.json.ObjectJsonParser;
import org.ecbox.sdk.internal.parser.xml.ObjectXmlParser;
import org.ecbox.sdk.internal.util.FileItem;
import org.ecbox.sdk.internal.util.RequestParamHolder;
import org.ecbox.sdk.internal.util.WebUtils;

/**
 * 连接客户端
 * 
 * File: DefaultEopClient.java<br/>
 * Description: <br/>
 *
 * Copyright: Copyright (c) 2012 ecbox.com<br/>
 * Company: ECBOX,Inc.<br/>
 *
 * @author chenxiaochun
 * @date 2014年5月25日
 * @version 1.0
 */
public class DefaultEopClient implements EopClient {

	private static final String APP_KEY = "appKey"; // v
	private static final String FORMAT = "format";
	private static final String METHOD = "method"; // v
	private static final String TIMESTAMP = "timestamp"; // v
	private static final String VERSION = "v";
	private static final String SIGN = "sign"; // v
	private static final String SIGN_METHOD = "signMethod";
	private static final String SDK_VERSION = "sdkVersion";
	private static final String ACCESS_KEY = "accessKey";

	private String serverUrl;
	private String appKey;
	private String appSecret;
	private String format = Constants.FORMAT_JSON;
	private String signMethod = Constants.SIGN_METHOD_MD5;

	private int connectTimeout = 3000;// 3秒
	private int readTimeout = 15000;// 15秒
	private boolean needCheckRequest = true;
	private boolean needEnableParser = true;

	public DefaultEopClient(String serverUrl, String appKey, String appSecret) {
		this.appKey = appKey;
		this.appSecret = appSecret;
		this.serverUrl = serverUrl;
	}

	public DefaultEopClient(String serverUrl, String appKey, String appSecret, String format) {
		this(serverUrl, appKey, appSecret);
		this.format = format;
	}

	public DefaultEopClient(String serverUrl, String appKey, String appSecret, String format, int connectTimeout,
			int readTimeout) {
		this(serverUrl, appKey, appSecret, format);
		this.connectTimeout = connectTimeout;
		this.readTimeout = readTimeout;
	}

	public DefaultEopClient(String serverUrl, String appKey, String appSecret, String format, int connectTimeout,
			int readTimeout, String signMethod) {
		this(serverUrl, appKey, appSecret, format, connectTimeout, readTimeout);
		this.signMethod = signMethod;
	}

	@Override
	public <T extends EopResponse> T execute(EopRequest<T> request) throws ApiException {
		return execute(request, null);
	}

	@Override
	public <T extends EopResponse> T execute(EopRequest<T> request, String accessKey) throws ApiException {
		EopParser<T> parser = null;
		if (this.needEnableParser) {
			if (Constants.FORMAT_XML.equals(this.format)) {
				parser = new ObjectXmlParser<T>(request.getResponseClass());
			} else {
				parser = new ObjectJsonParser<T>(request.getResponseClass());
			}
		}
		return _execute(request, parser, accessKey);
	}

	private <T extends EopResponse> T _execute(EopRequest<T> request, EopParser<T> parser, String accessKey)
			throws ApiException {
		if (this.needCheckRequest) {
			try {
				request.check();// if check failed,will throw ApiRuleException.
			} catch (ApiRuleException e) {
				T localResponse = null;
				try {
					localResponse = request.getResponseClass().newInstance();
				} catch (InstantiationException e2) {
					throw new ApiException(e2);
				} catch (IllegalAccessException e3) {
					throw new ApiException(e3);
				}
				localResponse.setErrorCode(e.getErrCode());
				localResponse.setMsg(e.getErrMsg());
				return localResponse;
			}
		}

		Map<String, Object> rt = doPost(request, accessKey);
		if (rt == null)
			return null;

		T tRsp = null;
		if (this.needEnableParser) {
			try {
				tRsp = parser.parse((String) rt.get("rsp"));
				tRsp.setBody((String) rt.get("rsp"));
			} catch (RuntimeException e) {
				EopLogger.logBizError((String) rt.get("rsp"));
				throw e;
			}
		} else {
			try {
				tRsp = request.getResponseClass().newInstance();
				tRsp.setBody((String) rt.get("rsp"));
			} catch (Exception e) {
			}
		}

		tRsp.setParams((EopHashMap) rt.get("textParams"));
		tRsp.setUrl((String) rt.get("url"));
		if (!tRsp.isSuccess()) {
			EopLogger.logErrorScene(rt, tRsp, appSecret);
		}
		return tRsp;
	}

	public <T extends EopResponse> Map<String, Object> doPost(EopRequest<T> request, String accessKey)
			throws ApiException {
		Map<String, Object> result = new HashMap<String, Object>();

		// 请求参数容器
		RequestParamHolder requestHolder = new RequestParamHolder();
		EopHashMap appParams = new EopHashMap(request.getTextParams());
		requestHolder.setAppParams(appParams);

		// 添加协议级请求参数
		EopHashMap proMustParams = new EopHashMap();
		proMustParams.put(METHOD, request.getApiMethodName());
		proMustParams.put(VERSION, "2.0");
		proMustParams.put(APP_KEY, appKey);
		Long timestamp = request.getTimestamp();// 允许用户设置时间戳
		if (timestamp == null) {
			timestamp = System.currentTimeMillis();
		}

		// 因为沙箱目前只支持时间字符串，所以暂时用Date格式
		DateFormat df = new SimpleDateFormat(Constants.DATE_TIME_FORMAT);
		df.setTimeZone(TimeZone.getTimeZone(Constants.DATE_TIMEZONE));
		proMustParams.put(TIMESTAMP, df.format(new Date(timestamp)));//
		requestHolder.setProMustParams(proMustParams);

		// 协议可选参数
		EopHashMap proOptParams = new EopHashMap();
		proOptParams.put(FORMAT, format);// 格式
		proOptParams.put(SIGN_METHOD, signMethod);// 签名方法
		proOptParams.put(ACCESS_KEY, accessKey);// 访问令牌
		proOptParams.put(SDK_VERSION, Constants.SDK_VERSION);// sdk版本
		requestHolder.setProOptParams(proOptParams);

		// 添加签名参数
		try {
			if (Constants.SIGN_METHOD_MD5.equals(signMethod)) {
				// 注意签名放到了协议必填参数中
				proMustParams.put(SIGN, EopUtils.signEopRequestNew(requestHolder, appSecret, false));
			} else if (Constants.SIGN_METHOD_HMAC.equals(signMethod)) {
				proMustParams.put(SIGN, EopUtils.signEopRequestNew(requestHolder, appSecret, true));
			} else {
				proMustParams.put(SIGN, EopUtils.signEopRequest(requestHolder, appSecret));
			}
		} catch (IOException e) {
			throw new ApiException(e);
		}

		StringBuffer urlSb = new StringBuffer(serverUrl);
		try {
			String sysMustQuery = WebUtils.buildQuery(requestHolder.getProMustParams(), Constants.CHARSET_UTF8);
			String sysOptQuery = WebUtils.buildQuery(requestHolder.getProOptParams(), Constants.CHARSET_UTF8);

			urlSb.append("?");
			urlSb.append(sysMustQuery);
			if (sysOptQuery != null & sysOptQuery.length() > 0) {
				urlSb.append("&");
				urlSb.append(sysOptQuery);
			}
		} catch (IOException e) {
			throw new ApiException(e);
		}

		String rsp = null;
		try {
			// 是否需要上传文件
			if (request instanceof EopUploadRequest) {
				EopUploadRequest<T> uRequest = (EopUploadRequest<T>) request;
				Map<String, FileItem> fileParams = EopUtils.cleanupMap(uRequest.getFileParams());
				rsp = WebUtils.doPost(urlSb.toString(), appParams, fileParams, connectTimeout, readTimeout);
			} else {
				rsp = WebUtils.doPost(urlSb.toString(), appParams, connectTimeout, readTimeout);
			}
		} catch (IOException e) {
			throw new ApiException(e);
		}
		result.put("rsp", rsp);
		result.put("textParams", appParams);
		result.put("protocalMustParams", proMustParams);
		result.put("protocalOptParams", proOptParams);
		result.put("url", urlSb.toString());
		return result;
	}

	public void setNeedCheckRequest(boolean needCheckRequest) {
		this.needCheckRequest = needCheckRequest;
	}

	public void setNeedEnableParser(boolean needEnableParser) {
		this.needEnableParser = needEnableParser;
	}

	public void setNeedEnableLogger(boolean needEnableLogger) {
		EopLogger.setNeedEnableLogger(needEnableLogger);
	}

}
